package com.embedsky.administrator.mycardemulation.executor;

import android.util.Log;

import com.embedsky.administrator.mycardemulation.util.CollectionUtils;

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolScheduledExecutor implements ScheduledExecutor {
    private final ScheduledExecutorService executorService;
    private static final String TAG = ThreadPoolScheduledExecutor.class.getSimpleName();

    private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() + 1;
    private final ConcurrentLinkedQueue<RunnableWithScheduleFuture> futures;

    public ThreadPoolScheduledExecutor() {
        executorService = new ScheduledThreadPoolExecutor(CORE_POOL_SIZE,
                r -> new Thread(r, "ThreadPoolScheduledExecutor"));
        futures = new ConcurrentLinkedQueue<>();
    }

    @Override
    public void postDelay(Runnable r, long delay) {
        if (r == null) {
            Log.e(TAG, "postDelay: runnable is null");
            return;
        }
        if (CollectionUtils.findItem(futures, (item) -> r == item.runnable) != null) {
            Log.e(TAG, "postDelay: runnable already scheduled");
            return;
        }
        ScheduledFuture<?> schedule = executorService.schedule(() -> {
            r.run();
            //remove task from futures when task is terminated
            cancel(r);
        }, delay, TimeUnit.MILLISECONDS);
        futures.add(new RunnableWithScheduleFuture(r, schedule));
    }

    @Override
    public void postInterval(Runnable r, long delay, long interval) {
        if (r == null) {
            Log.e(TAG, "postInterval: runnable is null");
            return;
        }
        if (CollectionUtils.findItem(futures, (item) -> r == item.runnable) != null) {
            Log.e(TAG, "postInterval: runnable already scheduled");
            return;
        }
        ScheduledFuture<?> scheduledFuture
                = executorService.scheduleWithFixedDelay(r, delay, interval, TimeUnit.MILLISECONDS);
        futures.add(new RunnableWithScheduleFuture(r, scheduledFuture));
    }

    @Override
    public void postIntervalRange(Runnable r, int delay, long interval, int range) {
        postIntervalRange(r, null, delay, interval, range);
    }

    @Override
    public void postIntervalRange(Runnable r, Runnable onTimeout, int delay, long interval, int range) {
        if (r == null) {
            Log.e(TAG, "postIntervalRange: runnable is null");
            return;
        }
        if (CollectionUtils.findItem(futures, (item) -> r == item.runnable) != null) {
            Log.e(TAG, "postIntervalRange: runnable already scheduled");
            return;
        }
        ScheduledFuture<?> scheduledFuture = executorService.scheduleWithFixedDelay(new Runnable() {
            int count = 0;

            @Override
            public void run() {
                count++;
                if (count <= range) {
                    Log.i(TAG, "IntervalRange count:" + count);
                    r.run();
                } else {
                    if (onTimeout != null) {
                        onTimeout.run();
                    }
                    cancel(r);
                }
            }
        }, delay, interval, TimeUnit.MILLISECONDS);
        futures.add(new RunnableWithScheduleFuture(r, scheduledFuture));
    }


    @Override
    public void cancel(ScheduledFuture future) {
        if (future == null || future.isDone() || future.isCancelled()) {
            Log.i(TAG, "ThreadPoolScheduledExecutor.cancel: future already canceled");
            return;
        }
        future.cancel(true);
        RunnableWithScheduleFuture runnableWithScheduleFuture
                = CollectionUtils.findItem(futures, (item) -> item.scheduledFuture == future);
        if (runnableWithScheduleFuture != null) {
            futures.remove(runnableWithScheduleFuture);
            Log.i(TAG, "ThreadPoolScheduledExecutor.cancel: remove from futures success," +
                    "now task count:" + futures.size());
        }
        Log.i(TAG, "ThreadPoolScheduledExecutor.cancel: cancel success");
    }

    @Override
    public void cancel(Runnable runnable) {
        if (runnable == null) {
            Log.i(TAG, "ThreadPoolScheduledExecutor.cancel: runnable already canceled");
            return;
        }
        RunnableWithScheduleFuture runnableWithScheduleFuture
                = CollectionUtils.findItem(futures, (item) -> item.runnable == runnable);
        if (runnableWithScheduleFuture != null) {
            cancel(runnableWithScheduleFuture.scheduledFuture);
        }
    }

    @Override
    public void cancelAll() {
        if (executorService.isShutdown()) {
            Log.i(TAG, "ThreadPoolScheduledExecutor.cancelAll: all future already canceled");
            return;
        }
        executorService.shutdownNow();
        Log.i(TAG, "ThreadPoolScheduledExecutor.cancelAll:cancelAll success ");
    }

    private static class RunnableWithScheduleFuture {
        Runnable runnable;
        ScheduledFuture scheduledFuture;

        RunnableWithScheduleFuture(Runnable runnable, ScheduledFuture scheduledFuture) {
            this.runnable = runnable;
            this.scheduledFuture = scheduledFuture;
        }
    }
}
